﻿using System;
using System.Runtime.Serialization;
using System.Text.RegularExpressions;

/// <summary>
/// The Common namespace.
/// </summary>
namespace WebChatApi.Common
{
    /// <summary>
    /// Struct for TimePin
    /// </summary>
    [DataContract]
    [KnownType(typeof(RecurrenceType))]
    public struct TimePin : ICloneable
    {
        #region Constants

        /// <summary>
        /// The XML_ recurrence type
        /// </summary>
        private const string xml_RecurrenceType = "RecurrenceType";

        /// <summary>
        /// The XML_ reference stamp
        /// </summary>
        private const string xml_ReferenceStamp = "ReferenceStamp";

        /// <summary>
        /// The XML_ end stamp
        /// </summary>
        private const string xml_EndStamp = "EndStamp";

        /// <summary>
        /// The string format
        /// </summary>
        private const string stringFormat = "{0}#{1}_{2}";

        /// <summary>
        /// The string regex
        /// </summary>
        private static Regex stringRegex = new Regex(string.Format(stringFormat,
            "<?RecurrenceType>([0-9]*)",
            "<?RecurrenceTicks>([0-9]*)",
            "<?EndTicks>([0-9]*)"), RegexOptions.Compiled | RegexOptions.IgnoreCase);

        #endregion

        #region Enum RecurrenceType

        /// <summary>
        /// Enum for recurrence type.
        /// </summary>
        [DataContract]
        public enum RecurrenceType
        {
            /// <summary>
            /// Value indicating it is none.
            /// </summary>
            [EnumMember]
            None = 0,
            /// <summary>
            /// Value indicating it is once.
            /// </summary>
            [EnumMember]
            Once = 1,
            /// <summary>
            /// Value indicating it is every hour.
            /// </summary>
            [EnumMember]
            EveryHour = 2,
            /// <summary>
            /// Value indicating it is every day.
            /// </summary>
            [EnumMember]
            Everyday = 3,
            /// <summary>
            /// Value indicating it is every 2 days.
            /// </summary>
            [EnumMember]
            EveryTwoDays = 4,
            /// <summary>
            /// Value indicating it is weekly.
            /// </summary>
            [EnumMember]
            Weekly = 6,
            /// <summary>
            /// Value indicating it is monthly.
            /// </summary>
            [EnumMember]
            Monthly = 7,
            /// <summary>
            /// Value indicating it is yearly.
            /// </summary>
            [EnumMember]
            Yearly = 8,
            /// <summary>
            /// Value indicating it is every sunday
            /// </summary>
            [EnumMember]
            EverySunday = 0x10,
            /// <summary>
            /// Value indicating it is every monday
            /// </summary>
            [EnumMember]
            EveryMonday = 0x20,
            /// <summary>
            /// Value indicating it is every tuesday
            /// </summary>
            EveryTuesday = 0x40,
            /// <summary>
            /// Value indicating it is every wednesday
            /// </summary>
            [EnumMember]
            EveryWednesday = 0x80,
            /// <summary>
            /// Value indicating it is every thursday
            /// </summary>
            [EnumMember]
            EveryThursday = 0x100,
            /// <summary>
            /// Value indicating it is every friday
            /// </summary>
            [EnumMember]
            EveryFriday = 0x120,
            /// <summary>
            /// Value indicating it is every saturday
            /// </summary>
            [EnumMember]
            EverySaturday = 0x140,
            /// <summary>
            /// Value indicating it is every work day.
            /// </summary>
            [EnumMember]
            EveryWorkday = EveryMonday | EveryTuesday | EveryWednesday | EveryThursday | EveryFriday
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the recurrence.
        /// </summary>
        /// <value>The recurrence.</value>
        [DataMember]
        public RecurrenceType Recurrence
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the reference stamp.
        /// </summary>
        /// <value>The reference stamp.</value>
        [DataMember]
        public DateTime ReferenceStamp
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the end stamp.
        /// </summary>
        /// <value>The end stamp.</value>
        [DataMember]
        public DateTime? EndStamp
        {
            get;
            set;
        }

        #endregion

        #region Constuctor

        #endregion

        /// <summary>
        /// Gets the next pin.
        /// </summary>
        /// <param name="nowStandardStamp">The now standard stamp.</param>
        /// <returns>System.Nullable{DateTime}.</returns>
        public DateTime? GetNextPin(DateTime nowStandardStamp)
        {
            DateTime? result = null;
            DateTime tmp;
            switch (this.Recurrence)
            {
                //// Everyday
                case RecurrenceType.Everyday:
                    tmp = new DateTime(
                        nowStandardStamp.Year,
                        nowStandardStamp.Month,
                        nowStandardStamp.Day,
                        ReferenceStamp.Hour,
                        ReferenceStamp.Minute,
                        ReferenceStamp.Second);
                    if (tmp < nowStandardStamp)
                    {
                        tmp = tmp.AddDays(1);
                    }
                    result = tmp;
                    break;
                //// EveryYear
                case RecurrenceType.Yearly:
                    tmp = new DateTime(
                        nowStandardStamp.Year,
                        ReferenceStamp.Month,
                        ReferenceStamp.Day,
                        ReferenceStamp.Hour,
                        ReferenceStamp.Minute,
                        ReferenceStamp.Second);
                    if (tmp < nowStandardStamp)
                    {
                        tmp = tmp.AddYears(1);
                    }
                    result = tmp;
                    break;
                //// Weekly
                case RecurrenceType.Weekly:
                    tmp = new DateTime(
                        nowStandardStamp.Year,
                        nowStandardStamp.Month,
                        nowStandardStamp.Day,
                        ReferenceStamp.Hour,
                        ReferenceStamp.Minute,
                        ReferenceStamp.Second);
                    if (tmp < nowStandardStamp)
                    {
                        tmp = tmp.AddDays(7);
                    }
                    result = tmp;
                    break;
                //// Monthly
                case RecurrenceType.Monthly:
                    tmp = new DateTime(
                        nowStandardStamp.Year,
                        nowStandardStamp.Month,
                        ReferenceStamp.Day,
                        ReferenceStamp.Hour,
                        ReferenceStamp.Minute,
                        ReferenceStamp.Second);
                    if (tmp < nowStandardStamp)
                    {
                        tmp = tmp.AddMonths(1);
                    }
                    result = tmp;
                    break;
                //// EveryHour
                case RecurrenceType.EveryHour:
                    tmp = new DateTime(
                        nowStandardStamp.Year,
                        nowStandardStamp.Month,
                        nowStandardStamp.Day,
                        nowStandardStamp.Hour,
                        ReferenceStamp.Minute,
                        ReferenceStamp.Second);
                    if (tmp < nowStandardStamp)
                    {
                        tmp = tmp.AddHours(1);
                    }
                    result = tmp;
                    break;
                //// Once
                case RecurrenceType.Once:
                    if (this.ReferenceStamp >= nowStandardStamp)
                    {
                        result = this.ReferenceStamp;
                    }
                    break;
                //// EveryTwoDays
                case RecurrenceType.EveryWorkday:
                    tmp = new DateTime(
                      nowStandardStamp.Year,
                      nowStandardStamp.Month,
                      nowStandardStamp.Day,
                      ReferenceStamp.Hour,
                      ReferenceStamp.Minute,
                      ReferenceStamp.Second);
                    if (tmp < nowStandardStamp)
                    {
                        tmp = tmp.AddDays(1);
                    }
                    if (tmp.DayOfWeek == DayOfWeek.Sunday)
                    {
                        tmp = tmp.AddDays(1);
                    }
                    else if (tmp.DayOfWeek == DayOfWeek.Saturday)
                    {
                        tmp = tmp.AddDays(2);
                    }
                    result = tmp;
                    break;
                //// EveryTwoDays
                case RecurrenceType.EveryTwoDays:
                    tmp = new DateTime(
                   nowStandardStamp.Year,
                   nowStandardStamp.Month,
                   nowStandardStamp.Day,
                   ReferenceStamp.Hour,
                   ReferenceStamp.Minute,
                   ReferenceStamp.Second);
                    if (tmp < nowStandardStamp)
                    {
                        tmp = tmp.AddDays(2);
                    }
                    result = tmp;
                    break;
                case RecurrenceType.None:
                default:
                    break;
            }

            return result;
        }

        /// <summary>
        /// Creates a new object that is a copy of the current instance.
        /// </summary>
        /// <returns>A new object that is a copy of this instance.</returns>
        public object Clone()
        {
            TimePin clonedObject = new TimePin();

            clonedObject.EndStamp = this.EndStamp;
            clonedObject.Recurrence = this.Recurrence;
            clonedObject.ReferenceStamp = this.ReferenceStamp;

            return clonedObject;
        }

        #region String serialization

        /// <summary>
        /// Returns a <see cref="System.String" /> that represents this instance.
        /// </summary>
        /// <returns>A <see cref="System.String" /> that represents this instance.</returns>
        public override string ToString()
        {
            return string.Format(stringFormat,
               (int)(this.Recurrence),
                this.ReferenceStamp.Ticks,
                this.EndStamp == null ? string.Empty : this.EndStamp.Value.Ticks.ToString());
        }

        /// <summary>
        /// Parses from string.
        /// </summary>
        /// <param name="timePinString">The time pin string.</param>
        /// <param name="timePinOutput">The time pin output.</param>
        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
        /// <exception cref="InvalidObjectException">timePinString</exception>
        public static bool TryParse(string timePinString, out TimePin timePinOutput)
        {
            try
            {
                timePinOutput = Parse(timePinString);
                return true;
            }
            catch
            {
                timePinOutput = new TimePin();
                return false;
            }
        }

        /// <summary>
        /// Parses the specified time pin string.
        /// </summary>
        /// <param name="timePinString">The time pin string.</param>
        /// <returns>TimePin.</returns>
        /// <exception cref="InvalidObjectException">timePinString;null</exception>
        public static TimePin Parse(string timePinString)
        {
            timePinString.CheckNullObject("timePinString");

            var matchResult = stringRegex.Match(timePinString);
            if (matchResult.Success)
            {
                return new TimePin
                {
                    Recurrence = (RecurrenceType)(matchResult.Result("{$RecurrenceType}").DBToInt32()),
                    ReferenceStamp = new DateTime(matchResult.Result("{$RecurrenceTicks}").DBToLong()),
                    EndStamp = new DateTime(matchResult.Result("{$EndTicks}").DBToLong())
                };
            }
            else
            {
                throw new InvalidObjectException("timePinString", null, timePinString);
            }
        }

        #endregion

        /// <summary>
        /// Converts the UTC automatic local time.
        /// </summary>
        /// <param name="utcDateTime">The UTC date time.</param>
        /// <param name="localTimeZone">The local time zone.</param>
        /// <returns>DateTime.</returns>
        public DateTime ConvertUTCToLocalTime(DateTime utcDateTime, TimeZoneInfo localTimeZone = null)
        {
            return ConvertUTCToLocalTime(utcDateTime, localTimeZone != null ? localTimeZone.BaseUtcOffset.TotalHours : 0);
        }

        /// <summary>
        /// Converts the UTC automatic local time.
        /// </summary>
        /// <param name="utcDateTime">The UTC date time.</param>
        /// <param name="utcOffset">The UTC offset.</param>
        /// <returns>DateTime.</returns>
        public DateTime ConvertUTCToLocalTime(DateTime utcDateTime, double utcOffset)
        {
            var dateTime = new DateTime(
                utcDateTime.Year,
                utcDateTime.Month,
                utcDateTime.Day,
                utcDateTime.Hour,
                utcDateTime.Minute,
                utcDateTime.Second, DateTimeKind.Local);
            dateTime = dateTime.AddHours(utcOffset);

            return dateTime;
        }

    }
}
